﻿#region 참조 네임스페이스

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;

#endregion

namespace Skying.Management
{
	public class TokenAdjuster
	{
		/// <summary>
		/// 프로세스의 액세스 토큰을 개방한다.
		/// </summary>
		/// <param name="processHandle">
		/// <para>액세스 토큰을 개방할 프로세스의 핸들을 설정한다.</para>
		/// <para>프로세스는 반드시 PROCES_QUERY_INFORMATION 접근 권한을 가져야 한다.</para>
		/// </param>
		/// <param name="desiredAccess">액세스 토큰 접근 유형을 정의하기 위한 액서스 마스크를 설정한다.</param>
		/// <param name="tokenHandle">새로이 개방된 액서스 토큰을 나타내는 핸들을 참조한다.</param>
		/// <returns>성공적이면 0 이외의 값이 반환된다. 실패하면 0을 반환한다.</returns>
		[DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
		static extern int OpenProcessToken(IntPtr processHandle, int desiredAccess, ref IntPtr tokenHandle);

		/// <summary>
		/// 열린 핸들을 닫는다.
		/// </summary>
		/// <param name="handle">닫고자 하는 핸들을 설정한다.</param>
		/// <returns>성공적이면 0 이외의 값이 반환된다. 실패하면 0을 반환한다.</returns>
		[DllImport("kernel32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
		static extern bool CloseHandle(IntPtr handle);

		/// <summary>
		/// <para>액세스 토큰의 권한을 부여하거나 제한한다.</para>
		/// <para>액세스 토큰의 권한을 부여하거나 제한하는 것은 TOKEN_ADJUST_PRIVILEGES 권한을 필요로 한다.</para>
		/// </summary>
		/// <param name="tokenHandle">권한을 부여하거나 제한할 액세스 토큰의 핸들을 설정한다.</param>
		/// <param name="disableAllPrivileges">
		/// <para>모든 권한을 제한할 지 여부를 결정한다.</para>
		/// <para>true로 설정될 경우, newState 파라메터를 무시하고 모든 권한을 제한한다.</para>
		/// <para>false로 설정될 경우, newState 파라메터에 따라 권한을 부여한다.</para>
		/// </param>
		/// <param name="newState">권한의 목록과 그 권한들의 속성을 결정하는 <see cref="TokenAdjuster.TOKEN_PRIVILEGES"/> 구조체의 핸들을 설정한다.</param>
		/// <param name="bufferLength">previousState 파라메터을 참조하는 버퍼의 크기를 결정한다.</param>
		/// <param name="previousState">액세스 토근의 권한이 수정되기 이전의 권한 정보의 핸들을 참조한다.</param>
		/// <param name="returnLength">previousState에 의해 참조되는 권한 정보의 버퍼 크기를 참조한다.</param>
		/// <returns>성공적이면 0 이외의 값이 반환된다. 실패하면 0을 반환한다.</returns>
		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
		static extern int AdjustTokenPrivileges(IntPtr tokenHandle, bool disableAllPrivileges, IntPtr newState, int bufferLength, IntPtr previousState, ref int returnLength);

		/// <summary>
		/// <para>특정 시스템 내의 권한 명칭에 대한 LUID(<see cref="TokenAdjuster.LUID"/>)를 검색한다.</para>
		/// <para>LUID에 대한 설명은 http://terms.naver.com/entry.nhn?cid=2954&docId=863054&mobile&categoryId=2954 를 참조한다.</para>
		/// </summary>
		/// <param name="lpSystemName">
		/// <para>시스템의 명칭을 지정한다.</para>
		/// <para>로컬 시스템을 지정하려면 null을 설정한다.</para>
		/// </param>
		/// <param name="lpName">권한의 명칭을 설정한다.</param>
		/// <param name="lpLuid">권한을 참조하는 LUID를 참조한다.</param>
		/// <returns>성공적이면 true, 실패하면 false를 반환한다.</returns>
		[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute]
		static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid);

		[StructLayout(LayoutKind.Sequential)]
		internal struct LUID
		{
			internal int LowPart;
			internal int HighPart;
		}

		[StructLayout(LayoutKind.Sequential)]
		struct LUID_AND_ATTRIBUTES
		{
			LUID Luid;
			int Attributes;
		}

		[StructLayout(LayoutKind.Sequential)]
		struct _PRIVILEGE_SET
		{
			int PrivilegeCount;
			int Control;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
			LUID_AND_ATTRIBUTES[] Privileges;
		}

		[StructLayout(LayoutKind.Sequential)]
		internal struct TOKEN_PRIVILEGES
		{
			internal int PrivilegeCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
			internal int[] Privileges;
		}

		const int SE_PRIVILEGE_ENABLED = 0x00000002;
		const int TOKEN_ADJUST_PRIVILEGES = 0X00000020;
		const int TOKEN_QUERY = 0X00000008;
		const int TOKEN_ALL_ACCESS = 0X001f01ff;
		const int PROCESS_QUERY_INFORMATION = 0X00000400;

		public static bool SetPrivilege(string lpszPrivilege, bool bEnablePrivilege)
		{
			bool retval = false;

			int ltkpOld = 0;

			IntPtr hToken = IntPtr.Zero;

			TOKEN_PRIVILEGES tkp = new TOKEN_PRIVILEGES() { Privileges = new int[3], PrivilegeCount = 1 };

			TOKEN_PRIVILEGES tkpOld = new TOKEN_PRIVILEGES() { Privileges = new int[3] };

			LUID tLUID = new LUID();

			if (bEnablePrivilege)
			{
				tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
			}
			else
			{
				tkp.Privileges[2] = 0;
			}

			if (LookupPrivilegeValue(null, lpszPrivilege, ref tLUID))
			{
				Process proc = Process.GetCurrentProcess();

				if (proc.Handle != IntPtr.Zero)
				{
					if (OpenProcessToken(proc.Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref hToken) != 0)
					{
						tkp.PrivilegeCount = 1;
						tkp.Privileges[2] = SE_PRIVILEGE_ENABLED;
						tkp.Privileges[1] = tLUID.HighPart;
						tkp.Privileges[0] = tLUID.LowPart;

						const int bufLength = 256;

						IntPtr tu = Marshal.AllocHGlobal(bufLength);

						Marshal.StructureToPtr(tkp, tu, true);

						if (AdjustTokenPrivileges(hToken, false, tu, bufLength, IntPtr.Zero, ref ltkpOld) != 0)
						{
							if (Marshal.GetLastWin32Error() == 0)
							{
								retval = true;
							}
						}

						TOKEN_PRIVILEGES tokp = (TOKEN_PRIVILEGES)Marshal.PtrToStructure(tu, typeof(TOKEN_PRIVILEGES));

						Marshal.FreeHGlobal(tu);
					}
				}
			}

			if (hToken != IntPtr.Zero)
			{
				CloseHandle(hToken);
			}

			return retval;
		}
	}
}